use super::{Poll, PollEvent};
use crate::{Error, Result};
use core::mem::MaybeUninit;
use core::ptr;
use hipool::{Allocator, Boxed, PoolAlloc};

pub(crate) type BoxedPollImpl<'a, A> = Boxed<'a, PollImpl<'a, A>, A>;

#[repr(C)]
pub(crate) struct PollImpl<'a, A: Allocator = PoolAlloc> {
    fd: i32,
    events: Boxed<'a, [MaybeUninit<libc::epoll_event>], A>,
}

impl<'a, A: Allocator + 'a> Drop for PollImpl<'a, A> {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd) };
    }
}

impl<'a, A: Allocator + 'a> PollImpl<'a, A> {
    pub fn new_in(pool: &'a A) -> Result<BoxedPollImpl<'a, A>> {
        let events = Boxed::uninit_slice_in::<libc::epoll_event>(pool, EPOLL_EVENT_MAX)?;
        let uninit = Boxed::uninit_in::<Self>(pool)?;
        let fd = unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) };
        if fd >= 0 {
            let poll = uninit.write(Self { fd, events });
            Ok(poll)
        } else {
            Err(Error::last())
        }
    }
}

impl<'a, A: Allocator + 'a> Poll for PollImpl<'a, A> {
    fn add_event(&mut self, fd: i32, events: u32, e: u64) -> Result<()> {
        let mut event = libc::epoll_event { events, u64: e };
        let ret =
            unsafe { libc::epoll_ctl(self.fd, libc::EPOLL_CTL_ADD, fd, ptr::addr_of_mut!(event)) };
        if ret == 0 {
            Ok(())
        } else {
            Err(Error::last())
        }
    }

    fn mod_event(&mut self, fd: i32, events: u32, e: u64) -> Result<()> {
        let mut event = libc::epoll_event { events, u64: e };
        let ret =
            unsafe { libc::epoll_ctl(self.fd, libc::EPOLL_CTL_MOD, fd, ptr::addr_of_mut!(event)) };
        if ret == 0 {
            Ok(())
        } else {
            Err(Error::last())
        }
    }

    fn del_event(&mut self, fd: i32) -> Result<()> {
        let mut event = libc::epoll_event { events: 0, u64: 0 };
        let ret =
            unsafe { libc::epoll_ctl(self.fd, libc::EPOLL_CTL_DEL, fd, ptr::addr_of_mut!(event)) };
        if ret == 0 {
            Ok(())
        } else {
            Err(Error::last())
        }
    }

    fn wait(&mut self, timeout: i32) -> impl Iterator<Item = PollEvent> + '_ {
        let events = self.events.as_ptr().cast::<libc::epoll_event>();
        loop {
            let cnt =
                unsafe { libc::epoll_wait(self.fd, events, self.events.len() as i32, timeout) };
            if cnt >= 0 {
                return unsafe {
                    Iter::new(self.events.as_slice::<libc::epoll_event>(cnt as usize))
                };
            } else if Error::last().errno != hierr::EINTR {
                panic!()
            }
        }
    }
}

struct Iter<'a> {
    events: &'a [libc::epoll_event],
    pos: usize,
}

impl<'a> Iter<'a> {
    fn new(events: &'a [libc::epoll_event]) -> Self {
        Self { events, pos: 0 }
    }
}

impl Iterator for Iter<'_> {
    type Item = PollEvent;
    fn next(&mut self) -> Option<Self::Item> {
        let pos = self.pos;
        self.events.get(pos).map(|e| {
            self.pos += 1;
            PollEvent(e.events, e.u64)
        })
    }
}

const EPOLL_EVENT_MAX: usize = 512;
